package com.liuqi.openai.keyword;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 模糊匹配后面的几个字符, 由{@link #maxEditDistance}决定最大能匹配几个字符
 *
 * 模糊匹配最多只能匹配{@link #FUZZY_MATCH_MAX_EDIT_DISTANCE}个字符
 *
 * {@link #priorityExactMatch}控制是否优先使用精确匹配, 如果精确匹配未提取到关键词, 则进行模糊匹配
 *
 * 例如 maxEditDistance = 1
 * <p>
 *     关键词: java
 *     输入: jav
 *     匹配: java
 * </p>
 *
 * @author liuqi
 * @date 2025/8/3
 **/
public class PostFuzzyMatchKeywordExtractor extends ExactMatchKeywordExtractor {

    private static final int PRE_MINI_MATCH_LENGTH = 2;

    /**
     * 前置最少要匹配多少个字符才可以进行模糊匹配, 默认值 {@link #PRE_MINI_MATCH_LENGTH}
     */
    private final int preMiniMatch;

    /**
     * 模糊匹配的最大编辑距离, 默认值 1
     */
    private final int maxEditDistance;

    /**
     * 是否优先精确匹配, 如果精确匹配到关键词, 将不会触发模糊匹配
     * 默认值: true
     */
    private final boolean priorityExactMatch;

    public PostFuzzyMatchKeywordExtractor(List<Dictionary> dictionaries) {
        this(dictionaries, null, PRE_MINI_MATCH_LENGTH, 1, true);
    }

    public PostFuzzyMatchKeywordExtractor(List<Dictionary> dictionaries,
                                             int maxEditDistance) {
        this(dictionaries, null, PRE_MINI_MATCH_LENGTH, maxEditDistance, true);
    }

    public PostFuzzyMatchKeywordExtractor(List<Dictionary> dictionaries,
                                          int preMiniMatch,
                                          int maxEditDistance) {
        this(dictionaries, null, preMiniMatch, maxEditDistance, true);
    }

    public PostFuzzyMatchKeywordExtractor(List<Dictionary> dictionaries,
                                             Set<String> stopWords,
                                             int preMiniMatch,
                                             int maxEditDistance,
                                             boolean priorityExactMatch) {
        super(dictionaries, stopWords);

        if (maxEditDistance > FUZZY_MATCH_MAX_EDIT_DISTANCE) {
            throw new IllegalArgumentException("the maximum distance for fuzzy matching cannot be greater than : " + FUZZY_MATCH_MAX_EDIT_DISTANCE);
        }

        if (preMiniMatch < PRE_MINI_MATCH_LENGTH) {
            throw new IllegalArgumentException("The minimum matching length before cannot be less than : " + PRE_MINI_MATCH_LENGTH);
        }

        this.preMiniMatch = preMiniMatch;
        this.maxEditDistance = maxEditDistance;
        this.priorityExactMatch = priorityExactMatch;
    }


    @Override
    protected MatchResult match(String text, int pos) {
        // 优先精确匹配, 如果匹配到关键词, 则不再进行模糊匹配
        if (priorityExactMatch) {
            MatchResult result = super.match(text, pos);
            if (result != null) {
                return result;
            }
        }

        return postFuzzyMatch(text, pos);
    }

    /**
     * 后置模糊匹配
     *
     * @param text  检索文本
     * @param pos   检索文本长度偏移量
     * @return
     */
    private MatchResult postFuzzyMatch(String text, int pos) {
        TrieNode current = root;
        TrieNode preNode = root;
        int end = pos;

        // 匹配前缀字符数
        int match = 0;

        // 检索次数
        int searchCount = 0;

        while (end < text.length()) {
            searchCount++;

            char ch = Character.toLowerCase(text.charAt(end));
            current = current.children.get(ch);
            if (current != null) {
                end++;
                match++;    // 匹配到了前缀, 匹配数量加1
                preNode = current;
            } else {
                break;
            }
        }

        // 小于前置最小匹配长度
        if (match < preMiniMatch) {
            return null;
        }
        return postFuzzyMatch(text, pos, end, searchCount, preNode);
    }

    private MatchResult postFuzzyMatch(String text, int pos, int end, int searchCount, TrieNode node) {
        // 匹配到的关键词结果
        List<String> keywords = new ArrayList<>(2);

        // 前缀字符
        String pre = text.substring(pos, end);

        // 由FuzzyMatchState进行模糊匹配后缀字符
        Deque<FuzzyMatchState> stack = new ArrayDeque<>();
        stack.push(new FuzzyMatchState(node, 0, pre, 0));

        while (!stack.isEmpty()) {
            searchCount++;

            // 当前模糊匹配
            FuzzyMatchState current = stack.pop();

            // 当前模糊匹配后缀节点
            TrieNode currNode = current.node;

            if (currNode.endOfWord) {
                // 模糊匹配的统一转小写
                keywords.add(current.currentWord.toLowerCase());
            }

            // 如果可编辑长度还有, 在已匹配的前置字符串上, 追加key
            if (current.edits < maxEditDistance) {
                for (Map.Entry<Character, TrieNode> entry : currNode.children.entrySet()) {
                    stack.push(new FuzzyMatchState(
                            entry.getValue(),
                            current.offset + 1,
                            current.currentWord + entry.getKey(),
                            current.edits + 1
                    ));
                }
            }

        }

        if (keywords.isEmpty()) {
            return null;
        }

        return new MatchResult(keywords, pre.length(), searchCount, true);
    }

}
